#include "threed.h"
#include "ui_threed.h"
#include "analyse.h"
#include <QDebug>

ThreeD::ThreeD(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ThreeD)
{
    ui->setupUi(this);
    setWindowTitle("3D绘图");
    setWindowIcon(QIcon(":/image/P_mathematics.svg.png"));
    ui->expr->setPlaceholderText("仅局限于小写的x和y，其他字母不可以。乘号*不可省略！！！");
    ui->expr->setAttribute(Qt::WA_InputMethodEnabled, false);//设置默认输入法为英文，方便输入

    groupBox = new Q3DSurface;//用于在三维空间中呈现和显示数据的表面图
    groupBox->axisX()->setTitle("X轴");
    groupBox->axisY()->setTitle("Y轴");
    groupBox->axisZ()->setTitle("Z轴");
    groupBox->axisY()->setRange(-15,15);

    QSurfaceDataProxy * proxy = new QSurfaceDataProxy;//用于将数据模型和 Q3DSurface 类中的表面图进行连接
    series = new QSurface3DSeries(proxy);
    groupBox->addSeries(series);
    //布局管理器可以自动调整控件的位置和大小，以保持良好的界面风格和交互体验
    QVBoxLayout* vLayout = new QVBoxLayout();//在一个垂直布局中放置子窗口部件
    //创建一个QWindow对象，并将其包装到QWidget中，以便将groupBox嵌入到QWidget中
    vLayout->addWidget(createWindowContainer(groupBox));
    ui->widget->setLayout(vLayout);

    //设置渐变颜色
    QLinearGradient color;
    //设置颜色在渐变中所占的比例位置
    color.setColorAt(0.0, Qt::darkGreen);
    color.setColorAt(0.5, Qt::yellow);
    color.setColorAt(0.8, Qt::red);
    color.setColorAt(1.0, Qt::darkRed);
    groupBox->seriesList().at(0)->setBaseGradient(color);
    groupBox->seriesList().at(0)->setColorStyle(Q3DTheme::ColorStyleRangeGradient);//使数据系列的颜色随其值的范围而变化

    dataArray = new QSurfaceDataArray;//储存数据
}

ThreeD::~ThreeD()
{
    delete ui;
}

void ThreeD::on_create_clicked()
{
    dataArray->clear();
    if(ui->expr->text()==nullptr)
    {
        QMessageBox::warning(this,"警告","你还未输入函数解析式！");
        return;
    }
    auto expr="z="+ui->expr->text().toStdString();
    std::vector<std::any> post;
    if(!Analyse::getPostfix(expr,post))
    {
        QMessageBox::warning(this,"警告","你输入的函数解析式有错误！");
        return;
    }
    double points[10000][3];
    int cnt=0;
    //获取该函数的点数据
    for(double x=-15.0;x<=15.0;x+=0.3)
        for(double y=-15.0;y<=15.0;y+=0.3)
        {
            double z;
            getResult(post,z,'x','y',x,y);
            points[cnt][0]=x;
            points[cnt][1]=y;
            points[cnt][2]=z;
            cnt++;
        }

    int NN=100;
    //XZ轴放在和Y轴一块设置范围会终止程序，不知道为什么；但是放在这可以运行
    groupBox->axisX()->setRange(-20,20);
    groupBox->axisZ()->setRange(-20,20);
    dataArray->reserve(NN);
    for(int i = 0;i < NN;i++){
         QSurfaceDataRow *newRow = new QSurfaceDataRow(NN);
         int index = 0;
         for(int j=0;j<NN;j++)
         {
             //默认情况下，Z轴代表深度，而不是高度
             //所以把z的值放在第二个，y的值放在第三个，这样好看一点，但是注意：显示的坐标y和z是相反的
             (*newRow)[index++].setPosition(QVector3D(points[j+i*NN][0],points[j+i*NN][2],points[j+i*NN][1]));
             *dataArray << newRow;
         }
         *dataArray << newRow;
    }
    series->dataProxy()->resetArray(dataArray);
}

void ThreeD::on_clear_clicked()
{
        dataArray->clear();
        series->dataProxy()->resetArray(dataArray);
        ui->expr->setText("");
}

bool ThreeD::getResult(const std::vector<std::any>& post ,double& ret, char k,char h, double x,double y)
{
    std::vector<double> stack;
    ret=0;
    try
    {
        for(int i=0;i<post.size();++i)
        {
            auto &item=post[i];
            //处理数
            if(item.type()==typeid(double))
                stack.push_back(std::any_cast<double>(item));
            else
            {
                char c =std::any_cast<char>(item);
                if(c==k)//为未知数x
                    stack.push_back(x);
                else if(c==h)
                    stack.push_back(y);
                else//运算符
                {
                    double tmp=0;
                    //函数 一个数进行计算
                    if(Analyse::isFuncOpt(c))
                    {
                        if(stack.size()<1)//至少需要一个数
                            return false;
                        double num=stack.back();
                        stack.pop_back();
                        switch (c)
                        {
                            case 's':
                                tmp=sin(num);
                                break;
                            case 'c':
                                tmp=cos(num);
                                break;
                            case 't':
                                tmp= tan(num);
                                break;
                            case 'g':
                                tmp=log10(num);
                                break;
                            case 'n':
                                tmp=log(num);
                                break;
                            default:
                                tmp=0;
                                break;
                        }
                    }
                    else // 双目运算符运算
                    {
                        if(stack.size()<2)
                            return false;
                        double num1=stack.back();
                        stack.pop_back();
                        double num2=stack.back();
                        stack.pop_back();
                        switch (c)
                        {
                            case '+':
                                tmp=num2+num1;
                                break;
                            case '-':
                                tmp=num2-num1;
                                break;
                            case '*':
                                tmp=num2*num1;
                                break;
                            case '/':
                                tmp=num2/num1;
                                break;
                            case '^':
                                tmp= pow(num2,num1);
                                break;
                            default:
                                tmp=0;
                                break;
                        }
                    }
                    stack.push_back(tmp);
                }
            }
        }
    }catch (...)
    {
        return false;
    }
    if(stack.size()!=1)
        return false;
    ret=stack.back();
    return true;
}
